/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.xmlrpc;

import javax.xml.namespace.QName;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * XML-RPCの再帰構造を持つ型の構文解析を行う全てのクラスの親クラスとなる抽象クラス。<br>
 * (このクラスはスレッドセーフではありません。)<br>
 * (ライブラリの使用者には公開したくない内部処理用のクラスなので、あえてパッケージスコープにしています。)<br>
 */
abstract class AbstractRecursiveTypeParserImpl extends AbstractXmlRpcParser {
	//==========================================================
	//フィールド

	/**
	 * 「value」タグの構文解析クラスのインスタンス
	 */
	private AbstractXmlRpcParser valueParser = null;

	/**
	 * 作業用のStringBuilder
	 */
	private StringBuilder sb = new StringBuilder();

	/**
	 * 「value」タグの処理中かどうかのフラグ
	 */
	private boolean inValueTag = false;


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//抽象メソッド

	/**
	 * 結果のオブジェクトを追加します。<br>
	 *
	 * @param obj 結果のオブジェクト
	 * @throws SAXException SAX例外
	 */
	protected abstract void addResult(Object obj) throws SAXException;


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//オーバーライドするメソッド

	/**
	 * 要素の開始通知を受け取ります。<br>
	 *
	 * @param uri 名前空間URI
	 * @param localName 前置修飾子を含まないローカル名
	 * @param qName 接頭辞を持つ修飾名
	 * @param attributes 要素に付加された属性
	 * @throws SAXException SAX例外
	 */
	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		//「value」タグの処理中でない場合
		if (!this.inValueTag) {
			//例外を投げる
			throw new SAXParseException("タグの階層構造が不正です。"
					+ new QName(uri, localName), getDocumentLocator());
		}

		//「value」タグの構文解析クラスのインスタンスがnullの場合
		if (this.valueParser == null) {
			//タグ名に応じて、適切な「value」タグの構文解析クラスのインスタンスをフィールドに設定
			setValueParserFromTagName(uri, localName);

			//自分自身のLocator オブジェクトを、引き継ぐ「value」タグの構文解析クラスのインスタンスに引き継ぐ
			this.valueParser.setDocumentLocator(getDocumentLocator());

			//「value」タグの構文解析クラスのインスタンスに文書の開始を通知する
			this.valueParser.startDocument();
		}

		//「value」タグの構文解析クラスのインスタンスに処理を委譲する
		this.valueParser.startElement(uri, localName, qName, attributes);
	}

	/**
	 * 要素の終了通知を受け取ります。<br>
	 *
	 * @param uri 名前空間URI
	 * @param localName 前置修飾子を含まないローカル名
	 * @param qName 接頭辞を持つ修飾名
	 * @throws SAXException SAX例外
	 */
	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		//「value」タグの処理中でない場合
		if (!this.inValueTag) {
			//例外を投げる
			throw new SAXParseException("タグの階層構造が不正です。"
					+ new QName(uri, localName), getDocumentLocator());
		}

		//「value」タグの構文解析クラスのインスタンスに処理を委譲する
		this.valueParser.endElement(uri, localName, qName);
	}

	/**
	 * 要素内の文字データの通知を受け取ります。<br>
	 * このクラスの実装では、文字配列の使用範囲に空白文字以外が含まれている場合は例外を投げます。<br>
	 *
	 * @param ch 文字配列
	 * @param start 文字配列内の開始位置
	 * @param length 文字配列から使用される文字数
	 * @throws SAXException SAX例外
	 */
	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		//「value」タグの処理中の場合
		if (this.inValueTag) {
			//「value」タグの構文解析クラスのインスタンスがnullの場合
			//(最後まで処理対象のタグが見つからなかった場合)
			//(よろしくないクライアントの場合、「string」タグを省略して値を送ってくることがある)
			if (this.valueParser == null) {
				//文字配列の使用範囲を作業用のStringBuilderに追加する
				this.sb.append(ch, start, length);
			}
			//それ以外の場合
			else {
				//「value」タグの構文解析クラスのインスタンスに処理を委譲する
				this.valueParser.characters(ch, start, length);
			}
		}
		//それ以外の場合
		else {
			//親クラスの処理を呼び出す
			super.characters(ch, start, length);
		}
	}

	/**
	 * 要素内の文字データの通知を受け取ります。<br>
	 * このクラスの実装では、文字配列の使用範囲に空白文字以外が含まれている場合は例外を投げます。<br>
	 *
	 * @param ch 文字配列
	 * @param start 文字配列内の開始位置
	 * @param length 文字配列から使用される文字数
	 * @throws SAXException SAX例外
	 */
	@Override
	public void ignorableWhitespace(char[] ch, int start, int length)
			throws SAXException {
		//「value」タグの処理中の場合
		if (this.inValueTag) {
			//「value」タグの構文解析クラスのインスタンスがnullの場合
			//(最後まで処理対象のタグが見つからなかった場合)
			//(よろしくないクライアントの場合、「string」タグを省略して値を送ってくることがある)
			if (this.valueParser == null) {
				//文字配列の使用範囲を作業用のStringBuilderに追加する
				this.sb.append(ch, start, length);
			}
			//それ以外の場合
			else {
				//「value」タグの構文解析クラスのインスタンスに処理を委譲する
				this.valueParser.ignorableWhitespace(ch, start, length);
			}
		}
		//それ以外の場合
		else {
			//親クラスの処理を呼び出す
			super.ignorableWhitespace(ch, start, length);
		}
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//内部処理用

	/**
	 * 「value」タグの開始通知を受け取ります。<br>
	 *
	 * @throws SAXException SAX例外
	 */
	protected void startValueTag() throws SAXException {
		//「value」タグの処理中かどうかのフラグをたてる
		this.inValueTag = true;

		//「value」タグの構文解析クラスのインスタンスをnullに戻す
		this.valueParser = null;
	}

	/**
	 * 「value」タグの終了通知を受け取ります。<br>
	 *
	 * @throws SAXException SAX例外
	 */
	protected void endValueTag() throws SAXException {
		//「value」タグの処理中でない場合
		if (!this.inValueTag) {
			//例外を投げる
			throw new SAXParseException("「value」タグが開始される前に終了されました。", getDocumentLocator());
		}

		//「value」タグの構文解析クラスのインスタンスがnullの場合
		//(最後まで処理対象のタグが見つからなかった場合)
		//(よろしくないクライアントの場合、「string」タグを省略して値を送ってくることがある)
		if (this.valueParser == null) {
			//作業用のStringBuilderの内容を結果として追加する
			addResult(this.sb.toString());

			//作業用のStringBuilderを空に戻す
			this.sb.setLength(0);
		}
		//それ以外の場合
		else {
			//「value」タグの構文解析クラスのインスタンスに文書の終了を通知する
			this.valueParser.endDocument();

			//「value」タグの構文解析クラスの結果のオブジェクトを、自分自身の結果として追加する
			addResult(this.valueParser.getResult());
		}


		//「value」タグの処理中かどうかのフラグをfalseに戻す
		this.inValueTag = false;

		//「value」タグの構文解析クラスのインスタンスをnullに戻す
		this.valueParser = null;
	}

	/**
	 * タグ名に応じて、適切な「value」タグの構文解析クラスのインスタンスをフィールドに設定します。<br>
	 *
	 * @param uri 名前空間URI
	 * @param localName 前置修飾子を含まないローカル名
	 * @throws SAXParseException タグ名に応じた構文解析クラスが存在しない場合
	 */
	protected void setValueParserFromTagName(String uri, String localName)
			throws SAXParseException {
		//拡張型の名前空間のURIの場合
		//(「ex:」で始まるタグの場合)
		if (XmlRpcUtil.EXTENSION_TYPE_NAMESPACE_URI.equals(uri)) {
			//「nil」タグの場合
			if ("nil".equals(localName)) {
				this.valueParser = new NilParser();
			}
			//「i1」タグの場合
			else if ("i1".equals(localName)) {
				this.valueParser = new I1Parser();
			}
			//「i2」タグの場合
			else if ("i2".equals(localName)) {
				this.valueParser = new I2Parser();
			}
			//「i8」タグの場合
			else if ("i8".equals(localName)) {
				this.valueParser = new I8Parser();
			}
			//「nil」タグの場合
			else if ("nil".equals(localName)) {
				this.valueParser = new NilParser();
			}
			//「float」タグの場合
			else if ("float".equals(localName)) {
				this.valueParser = new FloatParser();
			}
			//「bigdecimal」タグの場合
			else if ("bigdecimal".equals(localName)) {
				this.valueParser = new BigDecimalParser();
			}
			//「biginteger」タグの場合
			else if ("biginteger".equals(localName)) {
				this.valueParser = new BigIntegerParser();
			}
			//「dateTime」タグの場合
			else if ("dateTime".equals(localName)) {
				this.valueParser = new DateTimeParser();
			}
		}
		//それ以外の場合
		//(標準でサポートされている型の場合)
		else {
			//「string」タグの場合
			if ("string".equals(localName)) {
				this.valueParser = new StringParser();
			}
			//「i4」タグの場合
			else if ("i4".equals(localName)) {
				this.valueParser = new I4Parser();
			}
			//「int」タグの場合
			else if ("int".equals(localName)) {
				this.valueParser = new IntParser();
			}
			//「boolean」タグの場合
			else if ("boolean".equals(localName)) {
				this.valueParser = new BooleanParser();
			}
			//「i4」タグの場合
			else if ("i4".equals(localName)) {
				this.valueParser = new I4Parser();
			}
			//「double」タグの場合
			else if ("double".equals(localName)) {
				this.valueParser = new DoubleParser();
			}
			//「dateTime.iso8601」タグの場合
			else if ("dateTime.iso8601".equals(localName)) {
				this.valueParser = new DateTimeIso8601Parser();
			}
			//「base64」タグの場合
			else if ("base64".equals(localName)) {
				this.valueParser = new Base64Parser();
			}
			//「struct」タグの場合
			else if ("struct".equals(localName)) {
				this.valueParser = new StructParser();
			}
			//「array」タグの場合
			else if ("array".equals(localName)) {
				this.valueParser = new ArrayParser();
			}
		}

		//「value」タグの構文解析クラスのインスタンスがnullのままの場合
		//(最後まで処理対象のタグが見つからなかった場合)
		if (this.valueParser == null) {
			throw new SAXParseException("サポートされていない型です。: "
					+ new QName(uri, localName), getDocumentLocator());
		}
	}
}
